#include "log.h"
using namespace std;

//Log类的构造函数，初始化成员变量
Log::Log()
{
    //初始行号是0
    lineCount_ = 0;
    //初始日志没有同步
    isAsync_ = false;
    //初始的写线程是nullptr
    writeThread_ = nullptr;
    //初始的阻塞队列是nullptr
    deque_ = nullptr;
    //初始的日期是0
    toDay_ = 0;
    //初始的写文件指针是nullptr
    fp_ = nullptr;
}

//Log类的析构函数
Log::~Log()
{
    //判断写线程是否存在并且写线程是否可加入
    if(writeThread_ && writeThread_->joinable())
    {
        //循环判断阻塞队列是否为空
        while(!deque_->Empty())
        {
            //如果阻塞队列不是空，则刷新阻塞队列（通知一个消费者线程去消费）
            deque_->Flush();
        }
        //关闭阻塞队列
        deque_->Close();
        //等待写线程加入
        writeThread_->join();
    }
    //如果已经打开了文件（fp_ != nullptr）
    if(fp_)
    {
        //先加上锁，防止在关闭文件时，有其他线程在写入文件
        lock_guard<mutex> locker(mtx_);
        //刷新缓冲区
        flush();
        //将文件关闭
        fclose(fp_);
    }
}

//Log类的初始化函数
void Log::init(int level = 1,const char* path,const char* suffix,int maxQueueSize)
{
    //日志功能已开启
    isOpen_ = true;
    //日志等级
    level_ = level;
    //如果最大队列大小大于0
    if(maxQueueSize > 0)
    {
        //开启异步日志功能
        isAsync_ = true;
        //如果阻塞队列不存在
        if(!deque_)
        {
            //创建一个新的阻塞队列（最大队列大小为maxQueueSize，存储的类型是string）
            unique_ptr<BlockDeque<std::string>> newDeque(new BlockDeque<std::string>(maxQueueSize));
            //把新创建的阻塞队列移动赋值给deque_
            deque_ = std::move(newDeque);
            //创建一个新的写线程，线程要执行的函数是FlushLogThread
            std::unique_ptr<std::thread> NewThread(new thread(FlushLogThread));
            //将新创建的写线程移动赋值给writeThread_
            writeThread_ = std::move(NewThread);
        }
    }
    //如果最大队列大小小于等于0
    else 
    {
        //不能开启异步日志功能
        isAsync_ = false;
    }
    //队列，写线程，初始化完毕，将行数设置为0
    lineCount_ = 0;
    //获取当前时间
    time_t timer = time(nullptr);
    //将时间转换为本地时间
    struct tm* sysTime = localtime(&timer);
    //将本地时间转换为时间结构体
    struct tm t = *sysTime;
    //给路径赋值
    path_ = path;
    //给后缀赋值
    suffix_ = suffix;
    //初始化最大行数
    MAX_LINES_ = MAX_LINES;
    //创建一个字符数组fileName，长度为LOG_NAME_LEN，将里面的值初始化为0
    char fileName[LOG_NAME_LEN] = {0};
    //将时间格式化输出到fileName中
    snprintf(fileName,LOG_NAME_LEN-1,"%s/%04d_%02d_%02d%s",path_,t.tm_year+1900,t.tm_mon+1,t.tm_mday,suffix_);
    //给日期赋值
    toDay_ = t.tm_mday;
    {
        //先加上锁，防止在操作文件时，有其他线程在操作文件
        lock_guard<mutex> locker(mtx_);
        //清空缓冲区
        buff_.RetrieveAll();
        //如果已经打开了文件（文件指针！= nullptr）
        if(fp_)
        {
            //先输出当前缓冲区的内容
            flush();
            //然后关闭文件指针
            fclose(fp_);
        }
        //把文件指针指向新的文件
        fp_ = fopen(fileName,"a");
        //如果文件打开失败
        if(fp_ == nullptr)
        {
            //根据path_为路径创建目录
            mkdir(path_,0777);
            //创建文件
            fp_ = fopen(fileName,"a");
        }
        //断言日志文件是否打开成功
        assert(fp_ != nullptr);
    }
}
//Log类的获取日志等级函数
int Log::GetLevel()
{
    //先上锁，防止在获取日志等级时，有其他线程在设置日志等级
    lock_guard<mutex> locker(mtx_);
    //返回日志等级
    return level_;
}
//设置日志等级函数
void Log::SetLevel(int level)
{
    //先上锁，防止在设置日志等级时，有其他线程在设置日志等级
    lock_guard<mutex> locker(mtx_);
    //设置日志等级
    level_ = level;
}
//写日志的函数
void Log::write(int level,const char* format,...)
{
    //创建timeval结构体，now
    struct timeval now = {0,0};
    //获取当前时间
    gettimeofday(&now,nullptr);
    //获取当前的秒数
    time_t tSec = now.tv_sec;
    //将秒数转换为本地时间
    struct tm* sysTime = localtime(&tSec);
    //将本地时间转换为时间结构体
    struct tm t = *sysTime;
    //多参数格式化
    va_list vaList;
    //如果是新的一天，或者是日志文件的行数超过了最大行数
    if(toDay_ != t.tm_mday || (lineCount_ && (lineCount_ % MAX_LINES_ == 0)))
    {
        //先解锁，防止在切换日志文件时，有其他线程在写入文件
        unique_lock<mutex> locker(mtx_);
        //解锁
        locker.unlock();
        //创建新的文件名
        char newFile[LOG_NAME_LEN];
        //装载日期的字符串
        char tail[36] = {0};
        //格式化时间
        snprintf(tail,36,"%04d_%02d_%02d",t.tm_year+1900,t.tm_mon+1,t.tm_mday);
        //如果是新的一天
        if(toDay_ != t.tm_mday)
        {
            //如果是新的一天，就创建一个新的日志文件
            snprintf(newFile,LOG_NAME_LEN-72,"%s/%s%s",path_,tail,suffix_);
            //把新的一天的日期赋值给toDay_
            toDay_ = t.tm_mday;
            //如果是新的一天，就把行数设置为0
            lineCount_ = 0;
        }
        //如果不是新的一天
        else 
        {
            //如果不是新的一天，也就是说之前的日志已经写满了，就创建一个新的日志文件
            snprintf(newFile,LOG_NAME_LEN-72,"%s/%s-%d%s",path_,tail,(lineCount_/MAX_LINES_),suffix_);
        }
        //将锁重新上锁
        locker.lock();
        //刷新缓冲区
        flush();
        //关闭旧的文件指针
        fclose(fp_);
        //打开新的文件
        fp_ = fopen(newFile,"a");
        //断言日志文件是否打开成功
        assert(fp_ != nullptr);
    }
    {
        //先上锁，防止在写入日志时，有其他线程在写入日志
        unique_lock<mutex> locker(mtx_);
        //将日志的行数加1
        lineCount_++;
        //向buff_缓冲区中，从头写，写入日志日期信息
        int n = snprintf(buff_.BeginWrite(),128,"%d-%02d-%02d %02d:%02d:%02d.%06ld ",t.tm_year+1900,t.tm_mon + 1,t.tm_mday,t.tm_hour,t.tm_min,t.tm_sec,now.tv_usec);
        //后移buff缓冲区的写指针
        buff_.HasWritten(n);
        //将多参数解包
        va_start(vaList,format);
        //将多参数格式化写入buff_缓冲区
        int m = vsnprintf(buff_.BeginWrite(),buff_.WritableBytes(),format,vaList);
        //结束多参数格式化
        va_end(vaList);
        //后移buff缓冲区中的写指针
        buff_.HasWritten(m);
        //在buff_缓冲区中，追加一个换行符和一个空字符
        buff_.Append("\n\0",2);
        //如果是异步日志，并且队列不为空，并且队列没有满
        if(isAsync_ && deque_ && !deque_->Full())
        {
            //将buff_缓冲区中的日志信息，转换为字符串，然后压入队列中
            deque_->Push_back(buff_.RetrieveAllToStr());
        }
        //如果不是异步日志，或者队列为空，或者队列满了
        else
        {
            //将buff_缓冲区中的日志信息，转换为字符串，然后写入文件
            fputs(buff_.Peek(),fp_);
        }
        //清空buff缓冲区
        buff_.RetrieveAll();
    }
}
//Log类的添加日志等级标题函数
void Log::AppendLogLevelTitle_ (int level)
{
    switch(level)
    {
        case 0:
        {
            buff_.Append("[debug]：",9);
            break;
        }
        case 1:
        {
            buff_.Append("[info]：",9);
            break;
        }
        case 2:
        {
            buff_.Append("[warn]：",9);
            break;
        }
        case 3:
        {
            buff_.Append("[error]：",9);
            break;
        }
        default:
        {
            buff_.Append("[info]：",9);
            break;
        }
    }
}
//Log类的刷新缓冲区函数
void Log::flush()
{
    //如果是异步日志
    if(isAsync_)
    {
        deque_->Flush();
    }
    fflush(fp_);
}
//Log类的异步写入函数
void Log::AsynWrite_()
{
    //创建一个空的字符串str
    string str = "";
    //循环判断队列是否能弹出一个字符串
    while(deque_->Pop(str))
    {
        //可以弹出字符串，就上锁，防止在向文件写入的时候其他线程也要写入文件
        lock_guard<mutex> locker(mtx_);
        //将字符串写入文件
        fputs(str.c_str(),fp_);
    }
}
//Log类的单例模式函数
Log* Log::Instance()
{
    static Log inst;
    return &inst;
}
//Log类的刷新线程函数
void Log::FlushLogThread()
{
    Log::Instance()->AsynWrite_();
}
